/*
 * Copyright (C) 2015, Wazuh Inc.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <time.h>

#include "shared.h"
#include "../wazuh_modules/wmodules.h"
#include "../wazuh_modules/wm_vulnerability_scanner.h"

#include "../scheduling/wmodules_scheduling_helpers.h"

#include "../config/global-config.h"
#include "../config/indexer-config.h"

#include "../../wrappers/libc/stdio_wrappers.h"

char * test_path;

extern int __real_ReadConfig(int modules, const char *cfgfile, void *d1, void *d2);
int __wrap_ReadConfig(int modules, const char *cfgfile, void *d1, __attribute__((unused)) void *d2) {
    check_expected(modules);
    check_expected(cfgfile);
    return __real_ReadConfig(modules, test_path, d1, NULL);
}

typedef struct test_struct {
    wm_vulnerability_scanner_t *vulnerability_scanner_config;
} test_struct_t;

static int setup_test_read(void **state) {
    test_structure *test;
    os_calloc(1, sizeof(test_structure), test);
    test->module = NULL;
    *state = test;
    return 0;
}

static int teardown_test_read(void **state) {
    test_structure *test = *state;
    OS_ClearNode(test->nodes);
    OS_ClearXML(&(test->xml));
    if(test->module) {
        if (test->module->data) {
            if (((wm_vulnerability_scanner_t*)test->module->data)->vulnerability_detection) {
                cJSON_Delete(((wm_vulnerability_scanner_t*)test->module->data)->vulnerability_detection);
            }
            os_free(test->module->data);
        }
        os_free(test->module->tag);
        os_free(test->module);
    }
    os_free(test);
    return 0;
}

void test_read_custom_cti_url_full_configuration(void **state) {
    const char *string =
        "<enabled>yes</enabled>"
        "<index-status>no</index-status>"
        "<feed-update-interval>60m</feed-update-interval>"
        "<offline-url>file:///var/algo.tar.gz</offline-url>"
    ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(string, &(test->xml));

    test_path = "test_global.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"enabled\":\"yes\",\"index-status\":\"no\",\"feed-update-interval\":\"60m\",\"offline-url\":\"file:///var/algo.tar.gz\",\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

void test_read_empty_configuration(void **state) {
    const char *string = "";
    test_structure *test = *state;
    test->nodes = string_to_xml_node(string, &(test->xml));
    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{}");
    cJSON_free(json_result);
}

void test_read_empty_field_default_cti_url_configuration(void **state) {
    const char *string =
        "<enabled>yes</enabled>"
        "<index-status>no</index-status>"
        "<feed-update-interval></feed-update-interval>"
        "<offline-url>file:///var/algo.tar.gz</offline-url>"
    ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(string, &(test->xml));

    test_path = "test_global_no_cti_url.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"enabled\":\"yes\",\"index-status\":\"no\",\"feed-update-interval\":\"\",\"offline-url\":\"file:///var/algo.tar.gz\",\"cti-url\":\"https://cti.wazuh.com/api/v1/catalog/contexts/vd_1.0.0/consumers/vd_4.8.0\"}");
    cJSON_free(json_result);
}

void test_read_configuration(void** state) {
    test_path = "test_vulnerability_detection.conf";

    expect_value(__wrap_ReadConfig, modules, CWMODULE);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    wmodule* test_wmodule;
    os_calloc(1, sizeof(wmodule), test_wmodule);

    assert_int_equal(ReadConfig(CWMODULE, OSSECCONF, &test_wmodule, NULL), 0);

    wm_vulnerability_scanner_t* module_data = (wm_vulnerability_scanner_t*)test_wmodule->next->data;
    char* json_vd_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_vd_result,
                        "{\"enabled\":\"yes\",\"index-status\":\"yes\",\"feed-update-interval\":\"10h\"}");

    assert_ptr_not_equal(indexer_config, NULL);
    char* json_indexer_result = cJSON_PrintUnformatted(indexer_config);
    assert_string_equal(json_indexer_result,
                        "{\"enabled\":\"yes\","
                        "\"hosts\":[\"https://INDEXER-IP:9200\"],"
                        "\"ssl\":{\"certificate_authorities\":[\"CA\"],"
                        "\"certificate\":\"CERT\",\"key\":\"KEY\"}}");

    cJSON_free(json_vd_result);
    cJSON_free(json_indexer_result);
    cJSON_Delete(module_data->vulnerability_detection);
    wm_free(test_wmodule);
}

void test_read_duplicate_configuration(void **state) {
    test_path = "test_vulnerability_detection_duplicate_configuration.conf";

    expect_value(__wrap_ReadConfig, modules, CWMODULE);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    wmodule* test_wmodule;
    os_calloc(1, sizeof(wmodule), test_wmodule);

    assert_int_equal(ReadConfig(CWMODULE, OSSECCONF, &test_wmodule, NULL), 0);

    wm_vulnerability_scanner_t* module_data = (wm_vulnerability_scanner_t*)test_wmodule->next->data;
    char* json_vd_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_vd_result,
                        "{\"enabled\":\"yes\",\"index-status\":\"yes\",\"feed-update-interval\":\"10h\"}");

    assert_ptr_not_equal(indexer_config, NULL);
    char* json_indexer_result = cJSON_PrintUnformatted(indexer_config);
    assert_string_equal(json_indexer_result,
                        "{\"enabled\":\"yes\","
                        "\"hosts\":[\"https://INDEXER-IP:9200\"],"
                        "\"ssl\":{\"certificate_authorities\":[\"CA\"],"
                        "\"certificate\":\"CERT\",\"key\":\"KEY\"}}");

    cJSON_free(json_vd_result);
    cJSON_free(json_indexer_result);
    cJSON_Delete(module_data->vulnerability_detection);
    wm_free(test_wmodule);
}

void test_read_multiple_configuration(void **state) {
    test_path = "test_vulnerability_detection_multiple_configuration.conf";

    expect_value(__wrap_ReadConfig, modules, CWMODULE);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    wmodule* test_wmodule;
    os_calloc(1, sizeof(wmodule), test_wmodule);

    assert_int_equal(ReadConfig(CWMODULE, OSSECCONF, &test_wmodule, NULL), 0);

    wm_vulnerability_scanner_t* module_data = (wm_vulnerability_scanner_t*)test_wmodule->next->data;
    char* json_vd_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_vd_result,
                        "{\"enabled\":\"yes\",\"index-status\":\"no\",\"feed-update-interval\":\"24h\"}");

    assert_ptr_not_equal(indexer_config, NULL);
    char* json_indexer_result = cJSON_PrintUnformatted(indexer_config);
    assert_string_equal(json_indexer_result,
                        "{\"enabled\":\"yes\","
                        "\"hosts\":[\"https://INDEXER-IP-2:9200\"],"
                        "\"ssl\":{\"certificate_authorities\":[\"CA_2\"],"
                        "\"certificate\":\"CERT_2\",\"key\":\"KEY_2\"}}");

    cJSON_free(json_vd_result);
    cJSON_free(json_indexer_result);
    cJSON_Delete(module_data->vulnerability_detection);
    wm_free(test_wmodule);
}

void test_read_vd_duplicate_configuration(void **state) {
    const char *string =
        "<enabled>yes</enabled>"
        "<index-status>no</index-status>"
        "<feed-update-interval>60m</feed-update-interval>"
        "<offline-url>file:///var/algo.tar.gz</offline-url>"
        "<enabled>yes</enabled>"
        "<index-status>no</index-status>"
        "<feed-update-interval>60m</feed-update-interval>"
        "<offline-url>file:///var/algo.tar.gz</offline-url>"
        ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(string, &(test->xml));

    test_path = "test_global.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result,
                        "{\"enabled\":\"yes\","
                        "\"index-status\":\"no\","
                        "\"feed-update-interval\":\"60m\","
                        "\"offline-url\":\"file:///var/algo.tar.gz\","
                        "\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

void test_read_vd_multiple_configuration(void **state) {
    const char *string =
        "<enabled>yes</enabled>"
        "<index-status>no</index-status>"
        "<feed-update-interval>60m</feed-update-interval>"
        "<offline-url>file:///var/algo.tar.gz</offline-url>"
        "<enabled>yes</enabled>"
        "<index-status>yes</index-status>"
        "<feed-update-interval>24h</feed-update-interval>"
        "<offline-url>file:///var/algo_2.tar.gz</offline-url>"
        ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(string, &(test->xml));

    test_path = "test_global.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result,
                        "{\"enabled\":\"yes\","
                        "\"index-status\":\"yes\","
                        "\"feed-update-interval\":\"24h\","
                        "\"offline-url\":\"file:///var/algo_2.tar.gz\","
                        "\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

// Test reading just the old VD config.
void test_read_old_vd_config(void **state) {
    const char *old_vd_config = 
        "<enabled>yes</enabled>"
    ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(old_vd_config, &(test->xml));

    test_path = "test_global.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), true), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"enabled\":\"yes\",\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

// Test reading just the old VD config, ignoring any key different from 'enabled'.
void test_read_old_vd_config_with_ignored_keys(void **state) {
    const char *old_vd_config = 
        "<enabled>no</enabled>"
        "<index-status>true</index-status>"
        "feed-update-interval>0</feed-update-interval>"
    ;
    test_structure *test = *state;
    test->nodes = string_to_xml_node(old_vd_config, &(test->xml));

    test_path = "test_global.conf";

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);

    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), true), 0);
    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"enabled\":\"no\",\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

// Test reading the new VD config before reading the old config. The last should be ignored.
// This may happen if both configurations are present in the ossec.conf file.
void test_read_old_vd_config_after_new_vd_config(void **state) {
    test_path = "test_global.conf";
    test_structure *test = *state;

    // New VD config.
    const char *new_vd_config = 
        "<enabled>no</enabled>"
        "<feed-update-interval>10</feed-update-interval>"
    ;
    test->nodes = string_to_xml_node(new_vd_config, &(test->xml));
    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);
    OS_ClearNode(test->nodes);
    OS_ClearXML(&(test->xml));

    // Old VD config.
    const char *old_vd_config = 
        "<enabled>yes</enabled>"
    ;
    test->nodes = string_to_xml_node(old_vd_config, &(test->xml));
    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), true), 0);

    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"enabled\":\"no\",\"feed-update-interval\":\"10\",\"cti-url\":\"random.cti.url.com\"}");
    cJSON_free(json_result);
}

// Test reading the old VD config before reading the new config. The prior should be overwritten.
// This may happen if both configurations are present in the ossec.conf file.
void test_read_new_vd_config_overwrite_old_vd_config(void **state) {
    test_path = "test_global.conf";
    test_structure *test = *state;

    // Old VD config.
    const char *old_vd_config = 
        "<enabled>yes</enabled>"
    ;
    test->nodes = string_to_xml_node(old_vd_config, &(test->xml));

    expect_value(__wrap_ReadConfig, modules, CGLOBAL);
    expect_string(__wrap_ReadConfig, cfgfile, OSSECCONF);
    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), true), 0);
    OS_ClearNode(test->nodes);
    OS_ClearXML(&(test->xml));

    // New VD config.
    const char *new_vd_config = 
        "<enabled>no</enabled>"
        "<feed-update-interval>10</feed-update-interval>"
    ;
    test->nodes = string_to_xml_node(new_vd_config, &(test->xml));
    assert_int_equal(Read_Vulnerability_Detection(&(test->xml), test->nodes, &(test->module), false), 0);

    wm_vulnerability_scanner_t *module_data = (wm_vulnerability_scanner_t*)test->module->data;
    char * json_result = cJSON_PrintUnformatted(module_data->vulnerability_detection);
    assert_string_equal(json_result, "{\"cti-url\":\"random.cti.url.com\",\"enabled\":\"no\",\"feed-update-interval\":\"10\"}");
    cJSON_free(json_result);
}

int main(void) {
    const struct CMUnitTest tests_configuration[] = {
        cmocka_unit_test_setup_teardown(test_read_custom_cti_url_full_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_duplicate_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_multiple_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_vd_duplicate_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_vd_multiple_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_empty_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_empty_field_default_cti_url_configuration, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_old_vd_config, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_old_vd_config_with_ignored_keys, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_old_vd_config_after_new_vd_config, setup_test_read, teardown_test_read),
        cmocka_unit_test_setup_teardown(test_read_new_vd_config_overwrite_old_vd_config, setup_test_read, teardown_test_read)
    };
    return cmocka_run_group_tests(tests_configuration, NULL, NULL);
}
